﻿using System;
using System.Runtime.InteropServices;
using System.Threading;
using SCG = System.Collections.Generic;

using Nemerle.Assertions;
using Nemerle.Utility;

using GASS.OpenCL;
using GASS.OpenCL.OpenCLDriver;

namespace Nemerle.OpenCL {

    [InfoProperty(CLEventInfo.ReferenceCount, int, Flags(no_cache))]
    [InfoProperty(CLEventInfo.CommandExecutionStatus, CLExecutionStatus, Flags(no_cache))]
    [InfoProperty(CLEventInfo.CommandType, CLCommandType)]
    [EqualityByIdPattern]
    public sealed class Event : IDisposable {

        internal this(command_queue : CommandQueue, event_id : CLEvent, params host_ptrs : array[GCHandle]) {
            this.command_queue = command_queue;
            this.host_ptrs = Array.AsReadOnly(host_ptrs ?? array(0));
            this.handle = EventHandle(event_id);
            this.profiling_info = EventProfilingInfo(this);
        }

        handle : EventHandle;

        host_ptrs : System.Collections.ObjectModel.ReadOnlyCollection[GCHandle];

        [Accessor] command_queue : CommandQueue;

        [Accessor] profiling_info : EventProfilingInfo;

        public Id : CLEvent {
            get { handle.EntityId }
        }

        public IsCompleted : bool {
            get { CommandExecutionStatus == CLExecutionStatus.Complete }
        }

        public WaitOne() : void {
            unless(IsCompleted)
                CheckError(clWaitForEvents(1, array[Id]));
        }

        public static WaitAll(params events : array[Event]) : void {
            when(null != events && events.Length > 0) {
                def ids = Array.ConvertAll(events, _.Id);
                CheckError(clWaitForEvents(ids.Length :> uint, ids));
            }
        }

        public static WaitAll(events : SCG.IEnumerable[Event]) : void {
            when(null != events)
                WaitAll(SCG.List(events).ToArray());
        }

        #region IDisposable

        public Dispose() : void {
            when(null != handle)
                handle.Close();
            foreach(ptr when ptr.IsAllocated in host_ptrs)
                ptr.Free();
        }

        #endregion

        #region Utils

        internal static ConvertEventList(deps : array[Event]) : uint * array[CLEvent] {
            if(null == deps || deps.Length == 0)
                (0U, null)
            else
                (deps.Length :> uint, Array.ConvertAll(deps, _.Id))
        }

        #endregion

        [InfoProperty(CLEventProfilingInfo.Queued, ulong)]
        [InfoProperty(CLEventProfilingInfo.Submit, ulong)]
        [InfoProperty(CLEventProfilingInfo.Start, ulong)]
        [InfoProperty(CLEventProfilingInfo.End, ulong)]
        public sealed class EventProfilingInfo {

            internal this(evt : Event) {
                this.Id = evt.Id;
            }

            Id : CLEvent;

            private GetTimeSpan(start : ulong, end : ulong) : TimeSpan {
                def ticks = unchecked(((end - start) / 100) :> long);
                TimeSpan(ticks)
            }

            public QueueDuration : TimeSpan {
                get { GetTimeSpan(Queued, Start) }
            }

            public ExecDuration : TimeSpan {
                get { GetTimeSpan(Start, End) }
            }

            public TotalDuration : TimeSpan {
                get { GetTimeSpan(Queued, End) }
            }

        }

    }

}